#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define Ctrl_Reg_Driver_CNT			1		  	/* 设备号个数 */
#define Ctrl_Reg_Driver_NAME			"Ctrl_Reg_Driver"	/* 名字 */

typedef struct
{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
    unsigned long long int Current_Offset;
    void __iomem *Current_Reg;
}Ctrl_Reg_Driver_dev;

Ctrl_Reg_Driver_dev Ctrl_Reg_Driver;
static char readbuf[256];		/* 读缓冲区 */
static char writebuf[256];		/* 写缓冲区 */

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - offt 	: 要读取的物理地址
 * @return 			: 读取的字节数，如果为负值，表示读取失败
 */
static loff_t Ctrl_Reg_Driver_llseek(struct file *filp, loff_t offt,  int cnt)
{
    ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset = offt;
	return 0;
}


/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件，file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int Ctrl_Reg_Driver_open(struct inode *inode, struct file *filp)
{
    Ctrl_Reg_Driver.Current_Offset=0;
	filp->private_data = &Ctrl_Reg_Driver; /* 设置私有数据 */
	return 0;
}


/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的寄存器个数（32位寄存器最大值为64）
 * @param - offt 	: 要读取的物理地址
 * @return 			: 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t Ctrl_Reg_Driver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        reg_value=readl(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
        memcpy(&readbuf[i*4], &reg_value, 4);        
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }
    retvalue = copy_to_user(buf, readbuf, cnt*4);
	return retvalue;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件，表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的寄存器个数（32位寄存器最大值为64）
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数，如果为负值，表示写入失败
 */
static ssize_t Ctrl_Reg_Driver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;

    retvalue = copy_from_user(writebuf, buf, cnt*4);    
    if(retvalue!=0)
    {
        return retvalue;
    }
    
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        memcpy(&reg_value,&writebuf[i*4], 4);
        writel(reg_value, ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg); 
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }

	return retvalue;
}


/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int Ctrl_Reg_Driver_release(struct inode *inode, struct file *filp)
{
    // iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
	return 0;
}

/* 设备操作函数 */
static struct file_operations Ctrl_Reg_Driver_fops = {
	.owner = THIS_MODULE,
	.open = Ctrl_Reg_Driver_open,
	.read = Ctrl_Reg_Driver_read,
	.write = Ctrl_Reg_Driver_write,
	.release = 	Ctrl_Reg_Driver_release,
    .llseek = Ctrl_Reg_Driver_llseek,
};

static int __init Ctrl_Reg_Driver_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&Ctrl_Reg_Driver.devid, 0, Ctrl_Reg_Driver_CNT, Ctrl_Reg_Driver_NAME);	/* 申请设备号 */
    if(ret < 0) {
        pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", Ctrl_Reg_Driver_NAME, ret);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
    }
    Ctrl_Reg_Driver.major = MAJOR(Ctrl_Reg_Driver.devid);	/* 获取分配号的主设备号 */
    Ctrl_Reg_Driver.minor = MINOR(Ctrl_Reg_Driver.devid);	/* 获取分配号的次设备号 */

    printk("Ctrl_Reg_Driver major=%d,minor=%d\r\n",Ctrl_Reg_Driver.major, Ctrl_Reg_Driver.minor);
    Ctrl_Reg_Driver.cdev.owner = THIS_MODULE;
    cdev_init(&Ctrl_Reg_Driver.cdev, &Ctrl_Reg_Driver_fops);

    ret = cdev_add(&Ctrl_Reg_Driver.cdev, Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
	if(ret < 0)
    {
		unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
    }

    Ctrl_Reg_Driver.class = class_create(THIS_MODULE, Ctrl_Reg_Driver_NAME);
	if (IS_ERR(Ctrl_Reg_Driver.class)) 
    {
		cdev_del(&Ctrl_Reg_Driver.cdev);
        unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
	}

    Ctrl_Reg_Driver.device = device_create(Ctrl_Reg_Driver.class, NULL, Ctrl_Reg_Driver.devid, NULL, Ctrl_Reg_Driver_NAME);
	if (IS_ERR(Ctrl_Reg_Driver.device)) 
    {
		class_destroy(Ctrl_Reg_Driver.class);
        cdev_del(&Ctrl_Reg_Driver.cdev);
        unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
	}

    return 0;
}

static void __exit Ctrl_Reg_Driver_exit(void)
{
    // iounmap(Ctrl_Reg_Driver.Current_Reg);
    cdev_del(&Ctrl_Reg_Driver.cdev);/*  删除cdev */
	unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT); /* 注销设备号 */
	device_destroy(Ctrl_Reg_Driver.class, Ctrl_Reg_Driver.devid);
	class_destroy(Ctrl_Reg_Driver.class);
}

module_init(Ctrl_Reg_Driver_init);
module_exit(Ctrl_Reg_Driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MIKE_ZHOU");
MODULE_INFO(intree, "Y");
